/****************************************************************
 * Filename: drvDemo.c
 * -------------------------------
 * Copyright(C),
 * Author: zhenquan.qiu
 * Version: V1.0.0
 * Last modified: 01/03 2017 16:07
 * Description:
 *
 * Change Log:
 * NO.	Author		    Date		Modified
 * 00	zhenquan.qiu	01/03 2017
 ****************************************************************/

#include <linux/platform_device.h>
#include <linux/miscdevice.h>

#include <linux/interrupt.h>
#include <asm/io.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/semaphore.h>

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>

#include <linux/sched.h>
#include <asm/uaccess.h>

#include <linux/slab.h>
#include <linux/kernel.h>

#include <linux/proc_fs.h>
#include <linux/semaphore.h>

#include "busDemo.h"
#include "debugk.h"

#define DRVDEMO_SIZE (4 * 1024)
#define DEVICE_NAME "busdemo"

#define DRVDEMO_PROC_DEBUG  "debug"
#define DRVDEMO_PROC_GAIN   "gain"

#define DRVDEMO_MAGIC 'K'
#define DRVDEMO_MEM_CLEAR   _IO (DRVDEMO_MAGIC, 0)

#define DRVDEMO_MEM_SET     _IO (DRVDEMO_MAGIC, 1)

#define DRVDEMO_MEM_READ    _IOR(DRVDEMO_MAGIC, 2, int)
#define DRVDEMO_MEM_write   _IOW(DRVDEMO_MAGIC, 2, int)

struct drvdemo_drv {
    struct cdev   cdev;
    struct device *dev;
    struct class  *class_cdev;
    struct proc_dir_entry *p;
    dev_t devno;
    char buf_cmd[DRVDEMO_SIZE];
    struct semaphore sem;
};

struct drvdemo_drv *gdrvdemo_drv;
static unsigned int gDebugMask = LOG_ERROR;

//#define DRVDEMO_ATOMIC
//#define DRVDEMO_SPINLOCK
#define DRVDEMO_SEM

#ifdef DRVDEMO_ATOMIC
static atomic_t drvdemo_atomic = ATOMIC_INIT(1);
#endif
#ifdef DRVDEMO_SEM
static DEFINE_SEMAPHORE(drvdemo_lock);
#endif

static ssize_t drvdemo_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    int ret;
    loff_t pos = *f_pos;

    struct drvdemo_drv *dev = filp->private_data;

    FUNC_ENTER();

    if (down_interruptible(&dev->sem))
        return -ERESTARTSYS;

    if (pos >= DRVDEMO_SIZE) {
        pr_edbg("read pos too large");
        ret = 0;
        goto out;
    }

    if (count > (DRVDEMO_SIZE - pos))
        count = DRVDEMO_SIZE - pos;

    if (copy_to_user(buf, dev->buf_cmd + *f_pos, count)) {
        pr_edbg("copy to user faild");
        goto out;
    }

    *f_pos += count;
    ret = count;

    up(&dev->sem);

out:
    return ret;
}

static ssize_t drvdemo_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
    ssize_t ret = -ENOMEM;
    loff_t pos = *f_pos;
    struct drvdemo_drv *dev = filp->private_data;

    FUNC_ENTER();

    if (down_interruptible(&dev->sem))
        return -ERESTARTSYS;

    if (pos > DRVDEMO_SIZE) {
        pr_edbg("write pos too large");
        goto out;
    }

    if (count > (DRVDEMO_SIZE - pos))
        count = DRVDEMO_SIZE - pos;

    if (copy_from_user(dev->buf_cmd + *f_pos, buf, count)) {
        ret = -EFAULT;
        goto out;
    }

    *f_pos = +count;
    ret = count;

    up(&dev->sem);

out:
    return ret;
}

static int drvdemo_open(struct inode *inode, struct file *filp)
{
    struct drvdemo_drv *dev;

    FUNC_ENTER();

#ifdef DRVDEMO_ATOMIC
    if (!atomic_dec_and_test(&drvdemo_atomic)) {
        atomic_inc(&drvdemo_atomic);
        return -EBUSY;
    }
#endif
#ifdef DRVDEMO_SEM
    if (down_trylock(&drvdemo_lock))
        return -EBUSY;
#endif

    dev = container_of(inode->i_cdev, struct drvdemo_drv, cdev);
    filp->private_data = dev;

    return 0;
}

static int drvdemo_release(struct inode *inode, struct file *filp)
{
    FUNC_ENTER();

#ifdef DRVDEMO_ATOMIC
    atomic_inc(&drvdemo_atomic);
#endif
#ifdef DRVDEMO_SEM
    up(&drvdemo_lock);
#endif

    return 0;
}

loff_t drvdemo_llseek(struct file *filp, loff_t off, int whence)
{
    loff_t pos;

    FUNC_ENTER();

    pos = filp->f_pos;
    switch (whence) {
    case SEEK_SET:
        pos = off;
        break;
    case SEEK_CUR:
    case SEEK_END:
        pos += off;
        break;
    default:
        return -EINVAL;
    }

    if ((pos > DRVDEMO_SIZE) || (pos < 0))
        return -EINVAL;

    return filp->f_pos = pos;
}

static long drvdemo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    struct drvdemo_drv *dev = filp->private_data;

    FUNC_ENTER();

    if (down_interruptible(&dev->sem))
        return -ERESTARTSYS;

    if (arg > DRVDEMO_SIZE)
        return -EINVAL;

    switch (cmd) {
    case DRVDEMO_MEM_CLEAR:
        memset(dev->buf_cmd, 0, DRVDEMO_SIZE);
        break;
    case DRVDEMO_MEM_SET:
        memset(dev->buf_cmd, 0, DRVDEMO_SIZE);
        memset(dev->buf_cmd, '1', arg);
        break;
    }

    up(&dev->sem);

    return 0;
}

static struct file_operations drvdemo_ops = {
    .owner	 = THIS_MODULE,
    .write 	 = drvdemo_write,
    .read 	 = drvdemo_read,
    .release = drvdemo_release,
    .open    = drvdemo_open,
    .llseek  = drvdemo_llseek,
    .unlocked_ioctl = drvdemo_ioctl,
};

static unsigned int _hexAtoi(char *arr)
{
    int i, len;
    unsigned int n, temp = 0;

    len = strlen(arr);

    for (i=0; i<len; i++) {
        if (arr[i] > 'A' && arr[i] <= 'F')
            n = arr[i] - 'A' + 10;
        else if (arr[i] > 'a' && arr[i] <= 'f')
            n = arr[i] - 'a' + 10;
        else if (arr[i] >= '0' && arr[i] <= '9')
            n = arr[i] - '0';
        else 
            continue;

        temp = temp * 16 + n;
    }

    return temp;
}

static ssize_t drvdemo_proc_debug_write(struct file *filp, const char __user *buf, size_t count, loff_t *offset)
{
    char tmpBuf[5];
    int len;

    len = count;
    if (count >= sizeof(tmpBuf))
        len = sizeof(tmpBuf) -1;

    if (copy_from_user(tmpBuf, buf, len)) {
        pr_edbg("copy from user fail \n");
        return -EFAULT;
    }

    tmpBuf[len] = 0;

    gDebugMask = _hexAtoi(tmpBuf);

    return len;
}

static ssize_t drvdemo_proc_debug_read(struct file *filp, char __user *buf, size_t count, loff_t *offset)
{
    char tmpBuf[50];
    int len;
    loff_t pos;

    pos = *offset;
    if (pos < 0 || (long)pos != pos || count < 0)
        return -EIO;

    if (pos > 0)
        return 0;

    len = sprintf(tmpBuf, "debug mask: 0x%x \n", gDebugMask);
    if (count >= len) {
        unsigned long missing;
        missing = copy_to_user(buf, tmpBuf, len);
        if (0 != missing) {
            printk("error: copy to user missing %ld bytes \n", missing);
            len = -EFAULT;
        }
    } else {
        len = -EFAULT;
    }

    if (len >0)
        *offset = pos + len;

    return len;
}

static const struct file_operations drvdemo_debug_entry_operations = {
    .owner = THIS_MODULE,
    .read  = drvdemo_proc_debug_read,
    .write = drvdemo_proc_debug_write,
};
static const struct file_operations drvdemo_gain_entry_operations = {
    .owner = THIS_MODULE,
    .read  = NULL,
    .write = NULL,
};

static int drvdemo_proc_init(struct proc_dir_entry* proc_root)
{
    struct proc_dir_entry *p;

    p = proc_create_data(DRVDEMO_PROC_DEBUG, 0666, proc_root, &drvdemo_debug_entry_operations, NULL);
    if (NULL == p) {
        printk("create proc error \n");
        return ENOMEM;
    }

    p = proc_create_data(DRVDEMO_PROC_GAIN, 0666, proc_root, &drvdemo_gain_entry_operations, NULL);
    if (NULL == p) {
        printk("create proc error \n");
        return ENOMEM;
    }

    return 0;
}

int drvdemo_setup_cdev(void)
{
    int ret;

    FUNC_ENTER();

    cdev_init(&gdrvdemo_drv->cdev, &drvdemo_ops);
    gdrvdemo_drv->cdev.owner = THIS_MODULE;
    gdrvdemo_drv->cdev.ops = &drvdemo_ops;

    ret = cdev_add(&gdrvdemo_drv->cdev, gdrvdemo_drv->devno, 1);
    if (ret) {
        ret = -1;
        pr_edbg("add dev faild \n");
    }

    return ret;
}

static int drvdemo_probe(struct busdemo_device * dev)
{
    int ret;
    int dev_major = 0;

    FUNC_ENTER();

    gdrvdemo_drv = (struct drvdemo_drv *)kmalloc(sizeof(struct drvdemo_drv), GFP_KERNEL);
    if (!gdrvdemo_drv) {
        pr_edbg("kmalloc faild");
        ret = -ENOMEM;
        return ret;
    }

    gdrvdemo_drv->devno = MKDEV(dev_major, 0);

    if (dev_major) {
        ret = register_chrdev_region(gdrvdemo_drv->devno, 1, DEVICE_NAME);
    } else {
        ret = alloc_chrdev_region(&gdrvdemo_drv->devno, 0, 1, DEVICE_NAME);
        dev_major = MAJOR(gdrvdemo_drv->devno);
    }
    if (ret < 0) {
        pr_edbg("drvdemo init faild \n");
        ret = -1;
        goto out_err0;
    }

    ret = drvdemo_setup_cdev();
    if (ret < 0) {
        pr_edbg("drvdemo init faild \n");
        ret = -1;
        goto out_err1;
    }

    gdrvdemo_drv->class_cdev = class_create(THIS_MODULE, DEVICE_NAME);
    if (IS_ERR(gdrvdemo_drv->class_cdev)) {
        pr_edbg("class create faild \n");
        ret = PTR_ERR(gdrvdemo_drv->class_cdev);
        goto out_err1;
    }

    gdrvdemo_drv->dev = device_create(gdrvdemo_drv->class_cdev, NULL, gdrvdemo_drv->devno, NULL, DEVICE_NAME);
    if (IS_ERR(gdrvdemo_drv->dev)) {
        pr_edbg("device create faild \n");
        ret = PTR_ERR(gdrvdemo_drv->dev);
        goto out_err2;
    }

    gdrvdemo_drv->p = proc_mkdir(DEVICE_NAME, NULL);
    if (IS_ERR(gdrvdemo_drv->p)) {
        ret = PTR_ERR(gdrvdemo_drv->p);
        goto out_err2;
    }

    sema_init(&gdrvdemo_drv->sem, 1);

    drvdemo_proc_init(gdrvdemo_drv->p);

    return 0;

out_err2:
    class_destroy(gdrvdemo_drv->class_cdev);

out_err1:
    cdev_del(&gdrvdemo_drv->cdev);
    unregister_chrdev_region(gdrvdemo_drv->devno, 1);

out_err0:
    kfree(gdrvdemo_drv);

    return ret;
}

static int drvdemo_remove(struct busdemo_device * dev)
{
    FUNC_ENTER();

    device_destroy(gdrvdemo_drv->class_cdev, gdrvdemo_drv->devno);
    class_destroy(gdrvdemo_drv->class_cdev);
    cdev_del(&gdrvdemo_drv->cdev);
    unregister_chrdev_region(gdrvdemo_drv->devno, 1);
    remove_proc_entry(DRVDEMO_PROC_DEBUG, gdrvdemo_drv->p);
    remove_proc_entry(DRVDEMO_PROC_GAIN, gdrvdemo_drv->p);
    remove_proc_entry(DEVICE_NAME, NULL);

    kfree(gdrvdemo_drv);
  return 0;
}

static struct busdemo_driver drvdemo_driver = {
    .version = "$Revision: 1.21 $",
    .module = THIS_MODULE,
    .probe  = drvdemo_probe,
    .remove = drvdemo_remove,
    .driver = {
        .name = "bustest",
    },
};

static int __init drvdemo_init(void)
{
    FUNC_ENTER();

    return register_busdemo_driver(&drvdemo_driver);
}

static void __exit drvdemo_exit(void)
{
    FUNC_ENTER();

    unregister_busdemo_driver(&drvdemo_driver);
}

module_init(drvdemo_init);
module_exit(drvdemo_exit);

MODULE_AUTHOR("gnsyxiang <gnsyxiang@163.com>");
MODULE_LICENSE("Dual BSD/GPL");

